﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LPLib
{
    public class Polymer<T>
    {
        private T[] seq;
        public T[] Seq
        {
            get { return seq; }
            set { seq = value; }
        }
        private byte[] qual;
        public byte[] Qual
        {
            get { return qual; }
            set { qual = value; }
        }
        private string name;
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        static Complementer<T> complementer;

        public Polymer(MonomerEncoding<T> encoding, string name = "", T[] sequence = null, byte[] qual = null)
        {
            if (qual != null && sequence != null && qual.Length != sequence.Length)
            {
                throw new ArgumentException("The quality and sequence arrarys are not the same length.");
            }

            complementer = new Complementer<T>(encoding);
            this.name = name;
            if (sequence != null)
            {
                this.seq = sequence;
            }
            else
                this.seq = new T[0];

            this.qual = qual;
        }

        public Polymer(MonomerEncoding<T> encoding, string name, string sequence, byte[] qual = null)
        {
            if (qual != null && sequence != "" && qual.Length != sequence.Length)
            {
                throw new ArgumentException("The quality and sequence arrarys are not the same length.");
            }

            complementer = new Complementer<T>(encoding);
            this.name = name;
            if (sequence != null)
                this.seq = encoding.FromString(sequence);
            else
                this.seq = new T[0];

            this.qual = qual;
        }

        /// <summary>
        /// Produces a new nucleotide sequence by removing nucleotides from the left-hand (5') end of a given nucleotide sequence.
        /// </summary>
        /// <param name="seq">The nucleotide sequence to be trimmed.</param>
        /// <param name="numberToCut">The number of nucleotides to remove from the 5' end.</param>
        /// <returns>A new nucleotide sequence in the same encoding as the argument.</returns>
        public static T[] TrimLeft(T[] seq, int numberToCut)
        {
            if (numberToCut < 0 || numberToCut > seq.Length)
            {
                throw new ArgumentOutOfRangeException();
            }

            int len = seq.Length - numberToCut;
            T[] dummy = new T[len];
            Array.Copy(seq, numberToCut, dummy, 0, len);
            return dummy;
        }

        /// <summary>
        /// Produces a new nucleotide sequence by removing nucleotides from the right-hand (3') end of a given nucleotide sequence.
        /// </summary>
        /// <param name="seq">The nucleotide sequence to be trimmed.</param>
        /// <param name="numberToCut">The number of nucleotides to remove from the 3' end.</param>
        /// <returns>A new nucleotide sequence in the same encoding as the argument.</returns>
        public static T[] TrimRight(T[] seq, int numberToCut)
        {
            if (numberToCut > seq.Length || numberToCut < 0)
            {
                throw new ArgumentOutOfRangeException();
            }

            int len = seq.Length - numberToCut;
            T[] dummy = new T[len];
            Array.Copy(seq, dummy, len);
            return dummy;
        }

        /// <summary>
        /// Produces a new nucleotide sequence as a contiguous subsequence of a give nucleotide sequence. 
        /// </summary>
        /// <param name="seq">The nucleotide sequence to be trimmed.</param>
        /// <param name="startIndex">The index of the first nucleotide to be included in the subsequence.</param>
        /// <param name="endIndex">The index of tha last nucleotide to be included in the subsequence.</param>
        /// <returns>A new nucleotide sequence in the same encoding as the argument.</returns>
        public static T[] Subsequence(T[] seq, int startIndex, int endIndex)
        {
            if (startIndex < 0 || endIndex >= seq.Length || endIndex < startIndex - 1)
            {
                throw new ArgumentException("Indices are inappropriate for subsequence isolation: " + startIndex + "," + endIndex + "," + seq.Length);
            }
            int length = endIndex - startIndex + 1;
            if (length == 0)
                return new T[0];

            T[] dummy = new T[length];
            Array.Copy(seq, startIndex, dummy, 0, length);
            return dummy;
        }

        /// <summary>
        /// Trims this nucleotide sequence by removing nucleotides from the left-hand (5') end.
        /// </summary>
        /// <param name="numberToCut">The number of nucleotides to be removed from the 5' end.</param>
        public void TrimLeft(int numberToCut)
        {
            this.seq = TrimLeft(this.seq, numberToCut);
            if (this.qual != null)
                this.qual = Polymer<byte>.TrimLeft(this.qual, numberToCut);
        }

        /// <summary>
        /// Trims this nucleotide sequence by removing nucleotides from the right-hand (3') end.
        /// </summary>
        /// <param name="numberToCut">The number of nucleotides to be removed from the 3' end.</param>
        public void TrimRight(int numberToCut)
        {
            this.Seq = TrimRight(this.Seq, numberToCut);
            if (this.qual != null)
                this.qual = Polymer<byte>.TrimRight(this.qual, numberToCut);
        }

        /// <summary>
        /// Trims this nucleotide sequence by removing nucleotides from both ends.
        /// </summary>
        /// <param name="startIndex">The index of the first nucleotide to be retained.</param>
        /// <param name="endIndex">The index of the last nucleotide to be retained.</param>
        public T[] Subsequence(int startIndex, int endIndex)
        {
            return Subsequence(this.seq, startIndex, endIndex);
        }

        /// <summary>
        /// Produces the complement of a Polynucleotide.
        /// </summary>
        /// <param name="p">The polynucleotide to be complemented.</param>
        /// <returns>The polynucleotide whose sequence is the complement of the input polynucleotide. The name has ".C" appended.</returns>
        public Polymer<T> Complement()
        {
            return complementer.Complement(this);
        }

        public static Polymer<double[]> Normalize(Polymer<double[]> p)
        {
            double[][] normalizedSequence = new double[p.seq.Length][];
            for (int i= 0; i < normalizedSequence.Length; i++)
            {
                double norm = 0;
                for (int n = 0; n < 5; n++)
                {
                    norm += p.Seq[i][n];
                }

                if (norm == 0)
                    continue;

                normalizedSequence[i] = new double[5];
                for (int n = 0; n < 5; n++)
                {
                    normalizedSequence[i][n] = p.seq[i][n] / norm; ;
                }
            }
            return new Polymer<double[]>(new NucleotidePMF(), p.name + "-normalized", normalizedSequence);
        }

        public static double GetExpectedError(Polymer<double[]> p)
        {
            double expectedError = p.Seq.Length;
            foreach (double[] pmf in p.seq)
            {
                expectedError -= (NucleotidePMF.IDFlux(pmf, pmf) + pmf[4]*pmf[4]);
            }
            return expectedError;
        }
    }
}
